package se.kth.ict.id2203.assignment3b;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import se.kth.ict.id2203.console.Console;
import se.kth.ict.id2203.console.ConsoleLine;
import se.kth.ict.id2203.riwcm.AtomicRegister;
import se.kth.ict.id2203.riwcm.ReadRequest;
import se.kth.ict.id2203.riwcm.ReadResponse;
import se.kth.ict.id2203.riwcm.WriteRequest;
import se.kth.ict.id2203.riwcm.WriteResponse;

import se.sics.kompics.ComponentDefinition;
import se.sics.kompics.Handler;
import se.sics.kompics.Kompics;
import se.sics.kompics.Positive;
import se.sics.kompics.Start;
import se.sics.kompics.timer.ScheduleTimeout;
import se.sics.kompics.timer.Timer;

public final class Application3b extends ComponentDefinition {

	Positive<Console> con = requires(Console.class);
	
	Positive<Timer> timer = requires(Timer.class);
	Positive<AtomicRegister> riwcm = requires(AtomicRegister.class);

	private static final Logger logger =
			LoggerFactory.getLogger(Application3b.class);

	private List<String> commands;
	private boolean blocking;

	/**
	 * Instantiates a new application1a.
	 */
	public Application3b() {
		subscribe(handleInit, control);
		subscribe(handleStart, control);
		subscribe(handleContinue, timer);
		subscribe(handleConsoleInput, con);
		subscribe(handleReadReturn, riwcm);
		subscribe(handleWriteReturn, riwcm);
	}

	Handler<Application3bInit> handleInit = new Handler<Application3bInit>() {
		public void handle(Application3bInit event) {

			commands = new ArrayList<String>(Arrays.asList(event.getCommandScript().split(":")));
            commands.add("$DONE");
	        blocking = false;

		}
	};

	Handler<Start> handleStart = new Handler<Start>() {
		public void handle(Start event) {
			doNextCommand();
		}
	};

	Handler<ApplicationContinue> handleContinue = new Handler<ApplicationContinue>() {
		public void handle(ApplicationContinue event) {
			logger.info("Woke up from sleep");
			blocking = false;
			doNextCommand();
		}
	};
	
	Handler<ReadResponse> handleReadReturn = new Handler<ReadResponse>() {
		public void handle(ReadResponse event) {
			logger.info("==================>ReadResponse event received! [Register: "
					+ event.getRegister() + " Value: " + event.getVal() + "]");
			blocking = false;
			doNextCommand();
		}
	};
	
	Handler<WriteResponse> handleWriteReturn = new Handler<WriteResponse>() {
		public void handle(WriteResponse event) {
			logger.info("==================>WriteResponse event received! [Register: "
					+ event.getRegister() + "]");
			blocking = false;
			doNextCommand();
		}
	};

	Handler<ConsoleLine> handleConsoleInput = new Handler<ConsoleLine>() {
		@Override
		public void handle(ConsoleLine event) {
			commands.addAll(Arrays.asList(event.getLine().trim().split(":")));
			doNextCommand();
		}
	};

	private final void doNextCommand() {
		while (!blocking && !commands.isEmpty()) {
            doCommand(commands.remove(0));
		}
	}

	private void doCommand(String cmd) {
		if (cmd.startsWith("S")) {
			doSleep(Integer.parseInt(cmd.substring(1)));
		} else if (cmd.startsWith("X")) {
			doShutdown();
		} else if (cmd.equals("help")) {
			doHelp();
		} else if (cmd.startsWith("R")) {
			blocking = true;
			doRead(Integer.parseInt(cmd.substring(1)));
			
		} else if (cmd.startsWith("W")) {
			blocking = true;
			String wrst = cmd.substring(1);
			String wrarg[] = wrst.trim().split(",");
			doWrite(Integer.parseInt(wrarg[0]), Long.parseLong(wrarg[1]));
			
		} else if (cmd.equals("$DONE")) {
			logger.info("DONE ALL OPERATIONS");
		} else {
			logger.info("Bad command: '{}'. Try 'help'", cmd);
		}
	}

	private final void doHelp() {
		logger.info("Available commands: P<m>, L<m>, S<n>, help, X");
		logger.info("Pm: sends perfect message 'm' to all neighbors");
		logger.info("Lm: sends lossy message 'm' to all neighbors");
		logger.info("Sn: sleeps 'n' milliseconds before the next command");
		logger.info("help: shows this help message");
		logger.info("X: terminates this process");
	}

	private void doSleep(long delay) {
		logger.info("Sleeping {} milliseconds...", delay);

		ScheduleTimeout st = new ScheduleTimeout(delay);
		st.setTimeoutEvent(new ApplicationContinue(st));
		trigger(st, timer);
		
		blocking = true;
	}

	private void doShutdown() {
		System.out.println("2DIE");
		System.out.close();
		System.err.close();
		Kompics.shutdown();
		blocking = true;
	}
	
	private void doWrite(int register, long val) {
		logger.info("Trigger WriteResquest(" + register + ", " + val + ")");
		trigger(new WriteRequest(register, val), riwcm);		
	}
	
	private void doRead(int register) {
		logger.info("Trigger ReadResquest(" + register + ")");
		trigger(new ReadRequest(register), riwcm);	
	}
}
